﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Windows.Forms;
using Microsoft.WowAddonStudio.FrameXml.Serialization;
using Microsoft.WowAddonStudio.FrameXml.Serialization.LayoutFrames;
using Button = Microsoft.WowAddonStudio.FrameXml.Serialization.LayoutFrames.Button;
using StatusBar = Microsoft.WowAddonStudio.FrameXml.Serialization.LayoutFrames.StatusBar;

namespace Microsoft.WowAddonStudio.FrameXml.Components.Support
{
    public class FrameXmlComponentFactory
    {
        private static readonly Dictionary<Type, Type> componentMap = InitializeComponentMap();

        private static Dictionary<Type, Type> InitializeComponentMap()
        {
            return new Dictionary<Type, Type>
                       {
                           // Components
                           { typeof(Font), typeof(FontComponent) },
                           { typeof(GameTooltip), typeof(GameTooltipComponent) },

                           // Controls
                           { typeof(Ui), typeof(UiControl) },
                           { typeof(LayoutFrame), typeof(LayoutFrameControl) },
                           { typeof(Frame), typeof(FrameControl) },
                           { typeof(Texture), typeof(TextureControl) },
                           { typeof(FontString), typeof(FontStringControl) },
                           { typeof(Button), typeof(ButtonControl) },
                           { typeof(CheckButton), typeof(CheckButtonControl) },
                           { typeof(EditBox), typeof(EditBoxControl) },
                           { typeof(ColorSelect), typeof(ColorSelectControl) },
                           { typeof(StatusBar), typeof(StatusBarControl) },
                           { typeof(Slider), typeof(SliderControl) },
                           { typeof(ScrollFrame), typeof(ScrollFrameControl) },
                           { typeof(MessageFrame), typeof(MessageFrameControl) },
                           { typeof(ScrollingMessageFrame), typeof(ScrollingMessageFrameControl) },
                           { typeof(Minimap), typeof(MinimapControl) },
						   { typeof(MovieFrame), typeof(MovieFrameControl) },
                           { typeof(PlayerModel), typeof(PlayerModelControl) },
                           { typeof(TabardModel), typeof(TabardModelControl) },
                           { typeof(Model), typeof(ModelControl) },
                           { typeof(SimpleHTML), typeof(SimpleHTMLControl) },
						   { typeof(WorldFrame), typeof(WorldFrameControl) }
                       };
        }

        private readonly IComponentActivator activator;
        private readonly IContainer container;
    	private readonly IServiceProvider serviceProvider;

		/// <summary>
		/// Initializes a new instance of the <see cref="FrameXmlComponentFactory"/> class.
		/// </summary>
		/// <param name="activator">The activator to use to create the components.</param>
		/// <param name="container">The container to add the components to. Can be null.</param>
		/// <param name="serviceProvider">The service provider.</param>
        public FrameXmlComponentFactory(IComponentActivator activator, IContainer container, IServiceProvider serviceProvider)
        {
            if (activator == null)
                throw new ArgumentNullException("activator");
			if (serviceProvider == null)
				throw new ArgumentNullException("serviceProvider");

            this.activator = activator;
            this.container = container;
			this.serviceProvider = serviceProvider;
        }

		/// <summary>
		/// Initializes a new instance of the <see cref="FrameXmlComponentFactory"/> class.
		/// </summary>
		/// <param name="activator">The activator to use to create the components.</param>
		/// <param name="serviceProvider">The service provider.</param>
		public FrameXmlComponentFactory(IComponentActivator activator, IServiceProvider serviceProvider)
			: this(activator, null, serviceProvider)
        {
        }


        /// <summary>
        /// Creates a FrameXML control for a serialization object.
        /// </summary>
        /// <typeparam name="TSerializationObject">The type of serialization object.</typeparam>
        /// <typeparam name="TFrameXmlComponent">The type of the FrameXML component.</typeparam>
        /// <param name="serializationObject">The serialization object.</param>
        /// <returns>An instance of the <see cref="FrameXmlControl{TSerializationObject}"/> class.</returns>
        public TFrameXmlComponent CreateComponent<TSerializationObject, TFrameXmlComponent>(TSerializationObject serializationObject)
            where TSerializationObject : SerializationObject, new()
            where TFrameXmlComponent : IFrameXmlComponent
        {
            if (serializationObject == null)
                throw new ArgumentNullException("serializationObject");
            if (!componentMap.ContainsKey(typeof(TSerializationObject)))
                throw new ArgumentException("Serialization object type not mapped to FrameXML component.", "serializationObject");
            if (componentMap[typeof(TSerializationObject)] != typeof(TFrameXmlComponent))
                throw new ArgumentException("Serialization object does not map to FrameXML component.", "serializationObject");

            return (TFrameXmlComponent)this.CreateComponent(serializationObject);
        }

		/// <summary>
		/// Creates the component.
		/// </summary>
		/// <param name="serializationObject">The serialization object.</param>
		/// <returns></returns>
        public IFrameXmlComponent CreateComponent(SerializationObject serializationObject)
        {
            if (serializationObject == null)
                throw new ArgumentNullException("serializationObject");
            if (!componentMap.ContainsKey(serializationObject.GetType()))
                throw new ArgumentException("Serialization object type not mapped to FrameXML component.", "serializationObject");

            return this.CreateComponent(null, serializationObject);
        }

		/// <summary>
		/// Creates the component.
		/// </summary>
		/// <param name="parent">The parent.</param>
		/// <param name="serializationObject">The serialization object.</param>
		/// <returns></returns>
        private IFrameXmlComponent CreateComponent(IFrameXmlComponent parent, SerializationObject serializationObject)
        {
            Type componentType = componentMap[serializationObject.GetType()];

            // Create component for serialization object
            var component = (IFrameXmlComponent)activator.CreateComponent(componentType);

            // Set the serialization object
            component.SerializationObject = serializationObject;

            // Initialize the name of component.
            component.InitializeName();

            // Add component to container, if available
            if (container != null)
                container.Add(component);

            // Parent the component, if available
            if (parent != null)
                component.Parent = parent;

			// Initialize the component
            this.InitializeComponent(component);

            return component;
        }

		/// <summary>
		/// Initializes the component.
		/// </summary>
		/// <param name="frameXmlComponent">The frame XML component.</param>
        public void InitializeComponent(IFrameXmlComponent frameXmlComponent)
        {
            if (frameXmlComponent == null)
                throw new ArgumentNullException("frameXmlComponent");

            // If serialization object has child elements, create child components
            var serializationObjectContainer = frameXmlComponent.SerializationObject as ISerializationObjectContainer;
            if (serializationObjectContainer != null)
            {
				SerializationObject[] children = serializationObjectContainer.GetChildren(true).ToArray();

				for (int i = 0; i < children.Length; i++)
				{
                    // Create the child comoponent
                    this.CreateComponent(frameXmlComponent, children[i]);
                }
            }
        }

		/// <summary>
		/// Initializes the component.
		/// </summary>
		/// <param name="frameXmlComponent">The frame XML component.</param>
		public void ReinitializeComponent(IFrameXmlComponent frameXmlComponent)
		{
			if (frameXmlComponent == null)
				throw new ArgumentNullException("frameXmlComponent");

			// If serialization object has child elements, create child components
			var serializationObjectContainer = frameXmlComponent.SerializationObject as ISerializationObjectContainer;

			if (serializationObjectContainer != null)
			{
				//Get the DesignerHost instance
				var designerHost = (IDesignerHost) serviceProvider.GetService(typeof (IDesignerHost));

				//Get the ComponentChangeService instance
				var service = (IComponentChangeService) serviceProvider.GetService(typeof(IComponentChangeService));

				if (service != null)
				{
					var control = (Control) frameXmlComponent;

					//Get MemberDescriptor of controls collection
					var propertyDescriptor = TypeDescriptor.GetProperties(frameXmlComponent)["Controls"];
                    
					//Notify listeners about component changing
					service.OnComponentChanging(frameXmlComponent, propertyDescriptor);

                    // Gets the child elements of serialization object.
                    SerializationObject[] children = serializationObjectContainer.GetChildren(true).ToArray();

				    //Remove all sub-controls from the Site and from the DesignerHost
				    while (control.Controls.Count > 0)
				    {
                        var ctrl = control.Controls[0];

                        designerHost.DestroyComponent(ctrl);
                        frameXmlComponent.Site.Container.Remove(ctrl);
				    }

                    // Reinitialize frame xml components.
                    // It is important to use this instead of InitializeComponent(...) method.
                    // The InitializeComponent(...) creates the children controls after destrolying.
                    // At that moment the contols are not exist anymore.
                    for (int i = 0; i < children.Length; i++)
                    {
                        // Create the child comoponent
                        this.CreateComponent(frameXmlComponent, children[i]);
                    }

					//Notify listeners about component changed
					service.OnComponentChanged(frameXmlComponent, propertyDescriptor, control.Controls, control.Controls);
				}
			}
		}
    }
}
